Flutter Bottom Navigation: Sending Information from One Screen to Another
Bottom navigation is commonly used in Flutter applications to switch between primary sections such as Home, Products, Orders, Profile, and Settings. Flutter provides the older BottomNavigationBar widget and the Material 3 NavigationBar widget. For new Material 3 applications, NavigationBar is the preferred option. Flutter NavigationBar API
When using bottom navigation, you may also need to send information from one screen to another. For example, a Product screen may open a Product Details screen and send a product object, or a Profile screen may open an Edit Profile screen and send user information.
1. What Is Bottom Navigation?
Bottom navigation is a user interface pattern that places navigation options at the bottom of the application. It allows users to quickly switch between major sections of an application.
The traditional BottomNavigationBar is generally used for a small number of top-level views, typically around three to five. Flutter's Material 3 replacement is NavigationBar. Flutter BottomNavigationBar API
Common Bottom Navigation Items
- Home
- Products
- Orders
- Favorites
- Profile
Basic Structure
Scaffold
├── AppBar
├── Body
└── Bottom Navigation
├── Home
├── Products
├── Orders
└── Profile
2. NavigationBar in Material 3
NavigationBar is the Material 3 component for bottom navigation. It uses NavigationDestination widgets and the selectedIndex property to determine the currently selected destination. Flutter NavigationBar Documentation
NavigationBar(
selectedIndex: currentIndex,
onDestinationSelected: (index) {
setState(() {
currentIndex = index;
});
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.person_outline),
selectedIcon: Icon(Icons.person),
label: 'Profile',
),
],
)
3. BottomNavigationBar vs NavigationBar
| Feature |
BottomNavigationBar |
NavigationBar |
| Design system |
Material 2 style |
Material 3 style |
| Items property |
items |
destinations |
| Selection callback |
onTap |
onDestinationSelected |
| Selected index |
currentIndex |
selectedIndex |
| Destination widget |
BottomNavigationBarItem |
NavigationDestination |
| Recommended for new Material 3 apps |
No |
Yes |
Flutter's API documentation specifically describes NavigationBar as the updated version of BottomNavigationBar and explains the property changes when migrating. Flutter BottomNavigationBar Documentation
4. Creating a Basic Bottom Navigation App
The following example creates four sections: Home, Products, Orders, and Profile.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const MainScreen(),
);
}
}
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
@override
State createState() => _MainScreenState();
}
class _MainScreenState extends State {
int currentIndex = 0;
final List screens = const [
HomeScreen(),
ProductsScreen(),
OrdersScreen(),
ProfileScreen(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('My Application'),
),
body: screens[currentIndex],
bottomNavigationBar: NavigationBar(
selectedIndex: currentIndex,
onDestinationSelected: (index) {
setState(() {
currentIndex = index;
});
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.shopping_bag_outlined),
selectedIcon: Icon(Icons.shopping_bag),
label: 'Products',
),
NavigationDestination(
icon: Icon(Icons.receipt_long_outlined),
selectedIcon: Icon(Icons.receipt_long),
label: 'Orders',
),
NavigationDestination(
icon: Icon(Icons.person_outline),
selectedIcon: Icon(Icons.person),
label: 'Profile',
),
],
),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text('Home Screen'),
);
}
}
class ProductsScreen extends StatelessWidget {
const ProductsScreen({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text('Products Screen'),
);
}
}
class OrdersScreen extends StatelessWidget {
const OrdersScreen({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text('Orders Screen'),
);
}
}
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text('Profile Screen'),
);
}
}
How It Works
currentIndex stores the currently selected tab.
- The
screens list contains the screen widgets.
selectedIndex highlights the selected destination.
onDestinationSelected receives the selected index.
setState() updates the selected tab.
screens[currentIndex] displays the corresponding screen.
5. Sending Information from a Bottom Navigation Screen
Bottom navigation itself does not replace normal Flutter navigation. A tab can display a screen, and that screen can use Navigator.push() to open another route and pass information to it. Flutter's navigation cookbook demonstrates passing an object through a destination screen's constructor. Flutter Send Data to a New Screen
Products Screen
|
| User taps product
↓
Product Details Screen
|
| Product information
↓
Display product details
6. Passing a String from a Bottom Navigation Screen
Suppose the Products tab needs to open a Details screen and send the product name.
Products Screen
class ProductsScreen extends StatelessWidget {
const ProductsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProductDetailsScreen(
productName: 'Laptop',
),
),
);
},
child: const Text('View Laptop'),
),
),
);
}
}
Product Details Screen
class ProductDetailsScreen extends StatelessWidget {
final String productName;
const ProductDetailsScreen({
super.key,
required this.productName,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Product Details'),
),
body: Center(
child: Text(
'Product: $productName',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
);
}
}
7. Passing Multiple Values
You can send multiple values through the destination screen's constructor.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProductDetailsScreen(
productName: 'Laptop',
price: 55000,
category: 'Electronics',
),
),
);
The destination screen can receive them:
class ProductDetailsScreen extends StatelessWidget {
final String productName;
final double price;
final String category;
const ProductDetailsScreen({
super.key,
required this.productName,
required this.price,
required this.category,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(productName),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Product: $productName'),
Text('Price: ₹$price'),
Text('Category: $category'),
],
),
),
);
}
}
8. Passing a Custom Object from Bottom Navigation
Passing a model object is useful when a product, user, order, or other entity contains multiple related properties.
Product Model
class Product {
final int id;
final String name;
final double price;
final String category;
const Product({
required this.id,
required this.name,
required this.price,
required this.category,
});
}
Products Screen
class ProductsScreen extends StatelessWidget {
ProductsScreen({super.key});
final List products = const [
Product(
id: 1,
name: 'Laptop',
price: 55000,
category: 'Electronics',
),
Product(
id: 2,
name: 'Mobile',
price: 30000,
category: 'Electronics',
),
Product(
id: 3,
name: 'Headphones',
price: 3000,
category: 'Accessories',
),
];
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ListTile(
title: Text(product.name),
subtitle: Text('₹${product.price}'),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ProductDetailsScreen(product: product),
),
);
},
);
},
);
}
}
Product Details Screen
class ProductDetailsScreen extends StatelessWidget {
final Product product;
const ProductDetailsScreen({
super.key,
required this.product,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(product.name),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Text('ID: ${product.id}'),
Text('Category: ${product.category}'),
Text('Price: ₹${product.price}'),
],
),
),
);
}
}
9. Sending Information from Home Tab to Another Screen
A Home tab can also open another route and send information.
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const WelcomeScreen(
username: 'Manish',
),
),
);
},
child: const Text('Open Welcome Screen'),
),
);
}
}
class WelcomeScreen extends StatelessWidget {
final String username;
const WelcomeScreen({
super.key,
required this.username,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Welcome'),
),
body: Center(
child: Text(
'Welcome, $username!',
),
),
);
}
}
10. Sending User Information from Profile Tab
A Profile tab commonly opens an Edit Profile screen and passes the current user information.
User Model
class User {
final String name;
final String email;
final String phone;
const User({
required this.name,
required this.email,
required this.phone,
});
}
Profile Screen
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
final User user = const User(
name: 'Rahul',
email: '[email protected]',
phone: '9876543210',
);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(user.name),
Text(user.email),
Text(user.phone),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
EditProfileScreen(user: user),
),
);
},
child: const Text('Edit Profile'),
),
],
);
}
}
Edit Profile Screen
class EditProfileScreen extends StatelessWidget {
final User user;
const EditProfileScreen({
super.key,
required this.user,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Edit Profile'),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
TextField(
decoration: InputDecoration(
labelText: 'Name',
hintText: user.name,
),
),
TextField(
decoration: InputDecoration(
labelText: 'Email',
hintText: user.email,
),
),
TextField(
decoration: InputDecoration(
labelText: 'Phone',
hintText: user.phone,
),
),
],
),
),
);
}
}
11. Returning Information to a Bottom Navigation Screen
Information can also travel back from the newly opened screen to the bottom-navigation screen. The destination screen can call Navigator.pop(context, result), while the original screen awaits the result. Flutter Return Data from a Screen
Open Selection Screen
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
if (!mounted) return;
if (result != null) {
print('Selected: $result');
}
Return Data
class SelectionScreen extends StatelessWidget {
const SelectionScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Select Item'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context, 'Laptop');
},
child: const Text('Select Laptop'),
),
),
);
}
}
12. Bottom Navigation with ListView and Details Screen
This is a common application architecture:
Bottom Navigation
|
↓
Products Tab
|
↓
ListView
|
| Select Product
↓
Product Details
|
| Add to Cart
↓
Cart
Example
class ProductsScreen extends StatelessWidget {
ProductsScreen({super.key});
final products = const [
Product(
id: 1,
name: 'Laptop',
price: 55000,
category: 'Electronics',
),
Product(
id: 2,
name: 'Phone',
price: 30000,
category: 'Electronics',
),
];
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return Card(
child: ListTile(
title: Text(product.name),
subtitle: Text('₹${product.price}'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ProductDetailsScreen(
product: product,
),
),
);
},
),
);
},
);
}
}
13. Passing Only an ID
Sometimes a screen only needs an identifier rather than the complete object.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailsScreen(
productId: 101,
),
),
);
Destination:
class ProductDetailsScreen extends StatelessWidget {
final int productId;
const ProductDetailsScreen({
super.key,
required this.productId,
});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(
'Product ID: $productId',
),
),
);
}
}
This approach is useful when the details screen will fetch current product information from an API or database using the ID.
14. Passing Data Using RouteSettings
Flutter also supports passing data using RouteSettings.arguments. The destination can retrieve the value with ModalRoute.of(context). Flutter Passing Data Documentation
Send Data
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
settings: const RouteSettings(
arguments: 'Hello from Products',
),
),
);
Receive Data
class DetailsScreen extends StatelessWidget {
const DetailsScreen({super.key});
@override
Widget build(BuildContext context) {
final message =
ModalRoute.of(context)!.settings.arguments as String;
return Scaffold(
appBar: AppBar(
title: const Text('Details'),
),
body: Center(
child: Text(message),
),
);
}
}
15. Passing a Map Through RouteSettings
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
settings: const RouteSettings(
arguments: {
'id': 101,
'name': 'Laptop',
'price': 55000,
},
),
),
);
Read the Map:
final data =
ModalRoute.of(context)!.settings.arguments
as Map;
final id = data['id'];
final name = data['name'];
final price = data['price'];
16. Bottom Navigation with StatefulWidget
Bottom navigation usually requires a state value because the selected destination changes when the user taps different navigation items.
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
@override
State createState() => _MainScreenState();
}
class _MainScreenState extends State {
int selectedIndex = 0;
final screens = const [
HomeScreen(),
ProductsScreen(),
OrdersScreen(),
ProfileScreen(),
];
void changeTab(int index) {
setState(() {
selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: screens[selectedIndex],
bottomNavigationBar: NavigationBar(
selectedIndex: selectedIndex,
onDestinationSelected: changeTab,
destinations: const [
NavigationDestination(
icon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.shopping_bag),
label: 'Products',
),
NavigationDestination(
icon: Icon(Icons.receipt),
label: 'Orders',
),
NavigationDestination(
icon: Icon(Icons.person),
label: 'Profile',
),
],
),
);
}
}
17. Keeping the Bottom Navigation Bar Visible
A common requirement is to keep the bottom navigation visible while switching between top-level sections.
Scaffold(
body: screens[selectedIndex],
bottomNavigationBar: NavigationBar(
selectedIndex: selectedIndex,
onDestinationSelected: (index) {
setState(() {
selectedIndex = index;
});
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.person),
label: 'Profile',
),
],
),
)
The bottom navigation is supplied through the Scaffold.bottomNavigationBar property. Flutter Scaffold bottomNavigationBar API
18. Bottom Navigation and Navigator.push()
There are two different concepts that should not be confused:
| Feature |
Purpose |
| Bottom Navigation |
Switch between primary sections of an application. |
| Navigator.push() |
Open another route or detail screen. |
| Navigator.pop() |
Return to the previous route and optionally return a result. |
| selectedIndex |
Identifies the active bottom navigation destination. |
| Constructor parameter |
Transfers data to a destination widget. |
19. Complete Example: Bottom Navigation + Product Data Passing
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const MainScreen(),
);
}
}
class Product {
final int id;
final String name;
final double price;
const Product({
required this.id,
required this.name,
required this.price,
});
}
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
@override
State createState() => _MainScreenState();
}
class _MainScreenState extends State {
int selectedIndex = 0;
final screens = const [
HomeScreen(),
ProductsScreen(),
OrdersScreen(),
ProfileScreen(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Shopping App'),
),
body: screens[selectedIndex],
bottomNavigationBar: NavigationBar(
selectedIndex: selectedIndex,
onDestinationSelected: (index) {
setState(() {
selectedIndex = index;
});
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.shopping_bag_outlined),
selectedIcon: Icon(Icons.shopping_bag),
label: 'Products',
),
NavigationDestination(
icon: Icon(Icons.receipt_long_outlined),
selectedIcon: Icon(Icons.receipt_long),
label: 'Orders',
),
NavigationDestination(
icon: Icon(Icons.person_outline),
selectedIcon: Icon(Icons.person),
label: 'Profile',
),
],
),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text(
'Home',
style: TextStyle(fontSize: 25),
),
);
}
}
class ProductsScreen extends StatelessWidget {
const ProductsScreen({super.key});
final List products = const [
Product(
id: 1,
name: 'Laptop',
price: 55000,
),
Product(
id: 2,
name: 'Smartphone',
price: 30000,
),
Product(
id: 3,
name: 'Headphones',
price: 3000,
),
];
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return Card(
margin: const EdgeInsets.all(8),
child: ListTile(
title: Text(product.name),
subtitle: Text('₹${product.price}'),
trailing: const Icon(
Icons.arrow_forward_ios,
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ProductDetailsScreen(
product: product,
),
),
);
},
),
);
},
);
}
}
class ProductDetailsScreen extends StatelessWidget {
final Product product;
const ProductDetailsScreen({
super.key,
required this.product,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Product Details'),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 15),
Text('Product ID: ${product.id}'),
const SizedBox(height: 10),
Text(
'Price: ₹${product.price}',
style: const TextStyle(fontSize: 20),
),
],
),
),
);
}
}
class OrdersScreen extends StatelessWidget {
const OrdersScreen({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text(
'Orders',
style: TextStyle(fontSize: 25),
),
);
}
}
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text(
'Profile',
style: TextStyle(fontSize: 25),
),
);
}
}
20. Application Flow of the Complete Example
Main Screen
|
┌──────────┴──────────┐
↓ ↓
Bottom Navigation Selected Index
|
┌────────┼─────────┬─────────┐
↓ ↓ ↓ ↓
Home Products Orders Profile
|
| Tap Product
↓
Product Object
|
| Navigator.push()
↓
Product Details Screen
21. Sending Data from Orders Tab
An Orders tab can pass an order object to an Order Details screen.
class Order {
final int id;
final String customer;
final double total;
const Order({
required this.id,
required this.customer,
required this.total,
});
}
Navigate:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => OrderDetailsScreen(
order: order,
),
),
);
Receive:
class OrderDetailsScreen extends StatelessWidget {
final Order order;
const OrderDetailsScreen({
super.key,
required this.order,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Order #${order.id}'),
),
body: Column(
children: [
Text('Customer: ${order.customer}'),
Text('Total: ₹${order.total}'),
],
),
);
}
}
22. Sending Data from Profile to Settings
For example, the Profile tab can send the user's ID to Settings.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SettingsScreen(
userId: 101,
),
),
);
Settings screen:
class SettingsScreen extends StatelessWidget {
final int userId;
const SettingsScreen({
super.key,
required this.userId,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Settings'),
),
body: Center(
child: Text(
'Settings for User ID: $userId',
),
),
);
}
}
23. Maintaining Selected Tab After Returning
When a detail screen is opened with Navigator.push(), the underlying bottom-navigation screen remains in the navigation stack. When the detail screen is popped, the previous screen becomes visible again.
Products Tab
↓
Product Details
↓
Navigator.pop()
↓
Products Tab
This makes Navigator.push() useful for detail pages that are opened from a bottom-navigation destination.
24. Using BottomNavigationBar Instead of NavigationBar
Older Flutter applications may use BottomNavigationBar. It is still part of the Flutter API, but current Flutter documentation identifies NavigationBar as the updated component for Material 3 applications.
Scaffold(
body: screens[currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentIndex,
onTap: (index) {
setState(() {
currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Profile',
),
],
),
)
The API documentation explains that BottomNavigationBar.items, onTap, and currentIndex correspond to the newer NavigationBar.destinations, onDestinationSelected, and selectedIndex APIs. BottomNavigationBar API
25. Important Difference: Tab Switching vs Screen Navigation
| Scenario |
Recommended Pattern |
| Switch Home to Profile |
Change bottom navigation index |
| Products to Product Details |
Navigator.push() |
| Profile to Edit Profile |
Navigator.push() with user data |
| Selection screen to previous screen |
Navigator.pop(context, result) |
| API-based details page |
Pass an ID and fetch data |
| Application-wide shared data |
Use an appropriate state-management architecture |
26. Common Mistakes
Mistake 1: Creating a New Bottom Navigation Screen Instead of Navigating to Details
Top-level sections such as Home, Products, Orders, and Profile should generally be represented by bottom navigation destinations. A detail page can then be pushed from the selected section.
Mistake 2: Forgetting to Pass Required Data
class DetailsScreen extends StatelessWidget {
final String name;
const DetailsScreen({
super.key,
required this.name,
});
}
The navigation call must provide name.
Mistake 3: Using an Incorrect Type
ProductDetailsScreen(
product: product,
)
Make sure product is actually a Product object.
Mistake 4: Forgetting setState()
onDestinationSelected: (index) {
setState(() {
selectedIndex = index;
});
}
Without updating state, the selected bottom navigation destination may not update as expected.
Mistake 5: Passing Too Much Data
If a screen only needs a product ID, consider passing the ID rather than a large object.
27. Best Practices
- Use
NavigationBar for new Material 3 applications.
- Use bottom navigation for primary sections of the application.
- Use
Navigator.push() for detail and secondary screens.
- Use constructor parameters for clear, type-safe data passing.
- Use model classes for complex data.
- Pass only the information required by the destination.
- Pass IDs when the destination needs to fetch fresh data.
- Use
Navigator.pop() to return selected or edited data.
- Use typed navigation results such as
Navigator.push when appropriate.
- Check for null results when the user can leave a selection screen without choosing anything.
- Keep top-level navigation separate from detail-page navigation.
28. Real-World E-Commerce Example
A shopping application can use bottom navigation like this:
Shopping App
|
┌──────────┼──────────┐
↓ ↓ ↓
Home Products Cart
|
↓
Product List
|
↓
Select Product
|
↓
Product Details
|
↓
Add Cart
|
↓
Cart
When the user taps a product, the product object can be passed to the details screen.
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ProductDetailsScreen(
product: products[index],
),
),
);
}
29. Bottom Navigation with a Selection Screen
Suppose the Profile tab allows the user to choose a language.
final selectedLanguage = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const LanguageScreen(),
),
);
if (!mounted) return;
if (selectedLanguage != null) {
print('Language: $selectedLanguage');
}
Return the selection:
ListTile(
title: const Text('English'),
onTap: () {
Navigator.pop(context, 'English');
},
)
30. Bottom Navigation and State Management
For small applications, the selected index can be managed directly inside a StatefulWidget. As an application grows, shared application state may be handled using an appropriate state-management approach.
For example:
Bottom Navigation
|
↓
Application State
|
┌─────┼─────┐
↓ ↓ ↓
Cart User Favorites
The important idea is to distinguish between screen-specific navigation data and application-wide state.
31. Bottom Navigation on Larger Screens
Bottom navigation is primarily intended for compact layouts. Flutter's adaptive documentation discusses switching between bottom navigation and navigation rail depending on available screen space. Flutter Large Screen Navigation
Small Screen
↓
NavigationBar
↓
Home | Products | Profile
Large Screen
↓
NavigationRail
↓
Home
Products
Profile
32. Quick Revision
NavigationBar is the Material 3 bottom navigation component.
NavigationDestination defines each destination.
selectedIndex identifies the active destination.
onDestinationSelected handles destination selection.
BottomNavigationBar is the older Material navigation widget.
- Use
Navigator.push() to open a detail screen.
- Use constructor parameters to send strongly typed information.
- Use model classes to send complex objects.
- Use
Navigator.pop(context, result) to return information.
- Use IDs when the destination should load data independently.
- Keep top-level tab navigation separate from detail-page navigation.
33. Interview Questions
- What is bottom navigation in Flutter?
- What is the difference between
BottomNavigationBar and NavigationBar?
- Which widget is preferred for new Material 3 applications?
- What is the purpose of
selectedIndex?
- What does
onDestinationSelected do?
- How do you display different screens using bottom navigation?
- How can you send data from a Products tab to a Product Details screen?
- How can you pass a custom object between screens?
- How can you send only a product ID?
- How do you return data from a details or selection screen?
- What is the purpose of
Navigator.push()?
- What is the purpose of
Navigator.pop()?
- What is the difference between changing
selectedIndex and calling Navigator.push()?
- Why should complex data often be represented by model classes?
- When should an application use shared state instead of passing data through multiple screens?
34. Practical Exercise
Create a Flutter shopping application with four bottom navigation destinations.
Requirements
- Create Home, Products, Orders, and Profile tabs.
- Use
NavigationBar.
- Create a
Product model.
- Display at least five products in the Products tab.
- When the user taps a product, open Product Details.
- Pass the complete product object to Product Details.
- Display product name, ID, category, and price.
- Add an Add to Cart button.
- Return a result from a selection screen using
Navigator.pop().
- Display the returned information in the appropriate tab.
35. Mini Project Flow
Home
|
├── Featured Product
| ↓
| Product Details
|
Products
|
├── Product 1
├── Product 2
└── Product 3
↓
Product Details
↓
Cart
Orders
|
├── Order 101
└── Order 102
↓
Order Details
Profile
|
├── Edit Profile
└── Settings
↓
Settings Details
36. Key Takeaways
Flutter bottom navigation is useful for switching between the main sections of an application. The current Material 3 approach uses NavigationBar with NavigationDestination. When a bottom-navigation screen needs to open another screen, normal Flutter navigation APIs such as Navigator.push() can be used.
The most important pattern is:
Bottom Navigation
↓
Selected Tab
↓
User selects an item
↓
Navigator.push()
↓
Pass data through constructor
↓
Destination Screen
↓
Navigator.pop(context, result)
↓
Previous Screen receives result
Most Important Code Pattern
// Bottom navigation
NavigationBar(
selectedIndex: selectedIndex,
onDestinationSelected: (index) {
setState(() {
selectedIndex = index;
});
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.shopping_bag),
label: 'Products',
),
],
)
// Send data
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ProductDetailsScreen(
product: product,
),
),
)
// Receive data
class ProductDetailsScreen extends StatelessWidget {
final Product product;
const ProductDetailsScreen({
super.key,
required this.product,
});
@override
Widget build(BuildContext context) {
return Text(product.name);
}
}
// Return data
Navigator.pop(context, selectedValue);
37. Official Flutter Resources
38. Flutter Training Resources